package com.ndp.fb.walle.business.impl;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ndp.fb.data.api.service.schedulerservice.FBAdAccountsSchedulerService;
import com.ndp.fb.enums.ErrorType;
import com.ndp.fb.enums.adgroupstatus.AdGroupStatus;
import com.ndp.fb.enums.message.ChannelType;
import com.ndp.fb.enums.message.EventNotificationType;
import com.ndp.fb.enums.message.MessageStatus;
import com.ndp.fb.fbagent.service.AdGroupFBService;
import com.ndp.fb.fbagent.service.AdSetFBService;
import com.ndp.fb.model.fbagent.param.APIPriority;
import com.ndp.fb.model.fbagent.param.ApiCallBackTarget;
import com.ndp.fb.model.fbagent.param.adgroup.AdGroupParam;
import com.ndp.fb.model.fbagent.param.adset.AdSetParam;
import com.ndp.fb.mq.api.producer.RocketMqProducerService;
import com.ndp.fb.mq.api.service.messageservice.EventNotificationMessageService;
import com.ndp.fb.rdb.api.AdRdbService;
import com.ndp.fb.rdb.api.AdSetRdbService;
import com.ndp.fb.rdb.model.AdGroup;
import com.ndp.fb.rdb.model.AdSet;
import com.ndp.fb.rdb.model.message.EventNotificationMessage;
import com.ndp.fb.rulescript.api.OptimizationLogQuery;
import com.ndp.fb.rulescript.api.RuleResultQuery;
import com.ndp.fb.rulescript.model.OptLogBo;
import com.ndp.fb.rulescript.model.OptRecordBo;
import com.ndp.fb.util.StringUtil;
import com.ndp.fb.util.ThreadPool;
import com.ndp.fb.walle.business.RuleResultService;
import com.ndp.fb.walle.model.vo.ruleresult.param.RuleExecuteVoParam;
import com.ndp.fb.walle.util.ResponseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * Created by jacky.cheng on 2015/7/18.
 */
@Component
public class RuleResultServiceImpl implements RuleResultService {

    @Reference
    private RuleResultQuery ruleResultQuery;
    @Reference
    private FBAdAccountsSchedulerService adaccountsSchedulerService;
    @Reference
    private AdRdbService adGroupRdbService;
    @Reference
    private AdGroupFBService adGroupFBService;
    @Reference
    private AdSetFBService adSetFBService;
    @Reference
    private AdSetRdbService adSetRdbService;
    @Autowired
    private RocketMqProducerService rocketMqProducerService;
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Reference
    private OptimizationLogQuery optimizationLogQuery;
    @Reference
    private EventNotificationMessageService eventNotificationMessageService;

    @Override
    public void updateRuleResult(OptLogBo optLogBo) {
        ruleResultQuery.updateRuleResult(optLogBo);
    }


    @Override
    public OptRecordBo updateAdGroup(Long adGroupId, String status, RuleExecuteVoParam opt, String userTicket) {
        AdGroupParam fbAdGroupParam = new AdGroupParam();
        fbAdGroupParam.setAdGroupId(String.valueOf(adGroupId));
        fbAdGroupParam.setStatus(status);
        fbAdGroupParam.setFbAccountId(opt.getFbAccountId());
        String token = adaccountsSchedulerService.getAdaccountsToken(opt.getFbAccountId());
        fbAdGroupParam.setAccessToken(token);
        fbAdGroupParam.setApiPriorityCode(APIPriority.ModifyAdGroupStats.getCode());
        AdGroup adBefore = adGroupRdbService.findByAdGroupId(adGroupId);
        AdGroup adAfter=new AdGroup();
        BeanUtils.copyProperties(adBefore,adAfter);
        adAfter.setAdGroupStatus(status);
        asyncUpdateAdGroup(adGroupId, status, fbAdGroupParam,userTicket);
        return saveAdGroupOptimizationLog(adGroupId, status);
    }

    /**
     * 异步修改Ad
     * @param adGroupId
     * @param status
     * @param param
     * @param userTicket
     */
    private void asyncUpdateAdGroup(final Long adGroupId, final String status, final AdGroupParam param, final String userTicket) {
       ThreadPool.execute(new Runnable() {
           @Override
           public void run() {
               try {
                   adGroupFBService.updateAdGroupStats(param);
                   adGroupRdbService.updateAdGroupStatus(adGroupId, status);
               } catch (Exception e) {
                   String msg = ResponseUtils.getMsg(ErrorType.Rule_Update_AdGroup_Error,param.getAdGroupId());
                   sendFailureMessage(userTicket,msg);
               }
           }
       });
    }


    @Override
    public List<OptRecordBo> updateAdSet(RuleExecuteVoParam opt, String userTicket) {
        HashMap<String, Object> fieldMap = new HashMap<>();
        fieldMap.put("adSetId", opt.getAdSetId());
        AdSet adSetBefore = adSetRdbService.findByAdSetId(opt.getAdSetId());
        AdSet adSetAfter =new AdSet();
        BeanUtils.copyProperties(adSetBefore,adSetAfter);
        //修改其他属性
        AdSetParam param = new AdSetParam();
        param.setApiPriorityCode(APIPriority.ModifyAdSet.getCode());
        param.setFbAccountId(opt.getFbAccountId());
        param.setAdSetId(opt.getAdSetId() + "");
//        param.setCallBack(false);
        String token = adaccountsSchedulerService.getAdaccountsToken(opt.getFbAccountId());
        param.setAccessToken(token);
        //todo 不统一 assign to jacky
        if ("day".equals(opt.getBudgetType())) {
            param.setDailyBudget(opt.getBudgetNew());
            fieldMap.put("dailyBudget", opt.getBudgetNew());
            adSetAfter.setDailyBudget(opt.getBudgetNew());
        } else {
            param.setLifetimeBudget(opt.getBudgetNew());
            fieldMap.put("lifetimeBudget", opt.getBudgetNew());
            adSetAfter.setLifetimeBudget(opt.getBudgetNew());
        }
        if (opt.getStatus() != null) {
            param.setStatus(opt.getStatus());
            fieldMap.put("campaignStatus", opt.getStatus());
            adSetAfter.setCampaignStatus(opt.getStatus());
        }
        //修改预算且修改出价。。。出价延时调整。
        if (opt.getBidInfoNew() != null && opt.getBudgetNew() != null) {
            sendDelayMessage(opt, param);
        } else if (opt.getBidInfoNew() != null) {
            //直接修改出价
            JSONObject bidObject = JSONObject.parseObject(opt.getBidInfoNew());
            if (bidObject != null && !bidObject.isEmpty()) {
                for (String s : bidObject.keySet()) {
                    param.setBidAmount(bidObject.getInteger(s));
                }
            } else {
                logger.error("invalid bidInfo");
            }
            fieldMap.put("bidInfo", opt.getBidInfoNew());
            adSetAfter.setBidInfo(opt.getBidInfoNew());
            fieldMap.put("bid_amount", param.getBidAmount()); // fixme camel case
            adSetAfter.setBidAmount(param.getBidAmount());
        }
        asyncUpdateAdSet(fieldMap, param,userTicket);
        return saveAdSetOptimizationLog(opt);
    }

    /**
     * 异步修改adSet
     * @param fieldMap
     * @param param
     * @param userTicket
     */
    private void asyncUpdateAdSet(final HashMap<String, Object> fieldMap, final AdSetParam param, final String userTicket) {
        ThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    adSetFBService.updateAdSet(param);
                    //update mysql
                    List<Map<String, Object>> fields = new ArrayList<>();
                    fields.add(fieldMap);
                    adSetRdbService.updateAdSetsByAdSetIds(fields);
                } catch (Exception e) {
                    String msg = ResponseUtils.getMsg(ErrorType.Rule_Update_AdSet_Error,param.getCampaignId());
                    sendFailureMessage(userTicket,msg);
                }
            }
        });
    }

    private void sendDelayMessage(RuleExecuteVoParam opt, AdSetParam param) {
        if (!StringUtil.isNotEmpty(opt.getBidInfoNew()) || !StringUtil.isNotEmpty(opt.getBidInfoOld())) {
            logger.error("invalid param  adSetId:{}", opt.getAdSetId());
            return;
        }
        Map<String, Integer> bidInfoNewMap = (Map<String, Integer>) JSON.parse(opt.getBidInfoNew());
        Map<String, Integer> bidInfoOldMap = (Map<String, Integer>) JSON.parse(opt.getBidInfoOld());
        Integer vpiNew = null;
        Integer vpiOld = null;
        if (bidInfoOldMap != null && bidInfoNewMap != null) {
            if (bidInfoOldMap.size() == bidInfoNewMap.size() && bidInfoNewMap.size() == 1) {
                vpiOld = bidInfoOldMap.values().iterator().next();
                vpiNew = bidInfoNewMap.values().iterator().next();
                if (null != vpiNew && null != vpiOld) {
//                    Integer vpiOld = Integer.parseInt(vpiOldStr);
//                    Integer vpiNew = Integer.parseInt(vpiNewStr);
                    //如果单价是往低调，可以直接更新
                    if (vpiNew < vpiOld) {
//                        param.setBidInfo(opt.getBidInfoNew());
                        param.setBidAmount(vpiNew);
                    }
                    //补充其他特殊情况。。。
                }
            } else {
                //多出价后期优化
            }
        }
        //满足特殊情况时直接返回
        if (param.getBidAmount() != null) {
            logger.info("单价是往低调，可以直接更新.adSetId:{},bidAmount:{}",opt.getAdSetId(),vpiNew);
            //如果出价参数已设置，可以直接更新，就不用发送延时消息了。
            return;
        }
        //不满足特殊情况，发送延时消息，异步处理。延时5分钟。
        param.setBidInfo(opt.getBidInfoNew());
        param.setBidAmount(vpiNew);
        logger.info("发送延时消息，异步处理。延时5分钟.adSetId:{}，bidAmount:{}",opt.getAdSetId(),param.getBidAmount());
        rocketMqProducerService.sendMessage(ApiCallBackTarget.Business.UpdateASet.getModuleName(),
                ApiCallBackTarget.Business.UpdateASet.getTargetType(), UUID.randomUUID().toString(), param, 9);
        param.setBidAmount(null);
        param.setBidInfo(null);

    }

    private List<OptRecordBo> saveAdSetOptimizationLog(RuleExecuteVoParam opt) {
        List<OptRecordBo> records=new ArrayList<>();
        OptRecordBo record;
        if (opt.getBudgetNew() != null) {
            //修改预算
            record=new OptRecordBo();
            record.setAdSetId(opt.getAdSetId());
            //todo 不统一
            if ("day".equals(opt.getBudgetType())) {
                record.setOptType("dailyBudget");
            }else{
                record.setOptType("lifetimeBudget");
            }

            record.setBefore(opt.getBudgetOld() + "");
            record.setAfter(opt.getBudgetNew() + "");
            records.add(record);
        }
        if (opt.getStatus() != null) {
            //修改状态
            String before;
            if (AdGroupStatus.valueOf(opt.getStatus()) == AdGroupStatus.ACTIVE) {
                before = AdGroupStatus.PAUSED.getStatus();
            } else {
                before = AdGroupStatus.ACTIVE.getStatus();
            }
            record=new OptRecordBo();
            record.setAdSetId(opt.getAdSetId());
            record.setOptType("status");
            record.setBefore(before);
            record.setAfter(opt.getStatus());
            records.add(record);
        }
        if (opt.getBidInfoNew() != null) {
            //修改出价
            JSONObject bidInfoOld = JSONObject.parseObject(opt.getBidInfoOld());
            JSONObject bidInfoNew = JSONObject.parseObject(opt.getBidInfoNew());
            record=new OptRecordBo();
            record.setAdSetId(opt.getAdSetId());
            record.setOptType("price");
            record.setBefore((String) bidInfoOld.values().toArray()[0]);
            record.setAfter((String) bidInfoNew.values().toArray()[0]);
            records.add(record);
        }
        return records;
    }

    private OptRecordBo saveAdGroupOptimizationLog(Long adGroupId, String status) {
        String statusBefore;
        OptRecordBo recordBo=new OptRecordBo();
        if (AdGroupStatus.ACTIVE.getStatus().equals(status)) {
            statusBefore = AdGroupStatus.PAUSED.getStatus();
        } else {
            statusBefore = AdGroupStatus.ACTIVE.getStatus();
        }
        recordBo.setOptType("status");
        recordBo.setBefore(statusBefore);
        recordBo.setAfter(status);
        recordBo.setAdGroupId(adGroupId);
        return recordBo;
    }

    private void sendFailureMessage(String userTicket, String message) {
        EventNotificationMessage eventNotificationMessage = new EventNotificationMessage();
        EventNotificationType eventNotificationType = EventNotificationType.AD_OPTIMIZATION_FAILURE;
        eventNotificationMessage.setEventNo(com.ndp.fb.util.UUID.get());
        eventNotificationMessage.setEventType(eventNotificationType.getType());
        eventNotificationMessage.setIsNeedFunctionJump(false);
        eventNotificationMessage.setSender(userTicket);
        eventNotificationMessage.setReceiver(userTicket);
        eventNotificationMessage.setIsRead(0);
        eventNotificationMessage.setBody(message);
        eventNotificationMessage.setSendTime(System.currentTimeMillis());
        eventNotificationMessage.setStatus(MessageStatus.NORMAL.getStatus());
        List<ChannelType> channelTypeList = new ArrayList<>();
        channelTypeList.add(ChannelType.WEBSITE);
        eventNotificationMessageService.sendMessage(eventNotificationMessage, channelTypeList);
    }
}
